Vue - Object.defineProperty & Proxy


Posted by TempuraEngineer on 2023-04-15

目錄


Vue2

Object.defineProperty()

defineProperty()是Object的靜態方法,透過其可以在物件上新增屬性,或修改物件的屬性

它接收3個參數分別為obj、property、descriptor

其中descriptor會是物件,分為data descriptors(帶有value、writable屬性的物件)、accessor descriptors(帶有getter和setter的物件)

注意2種不能混用,不然會得到"Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"

If a descriptor has neither of value, writable, get and set keys, it is treated as a data descriptor. If a descriptor has both [value or writable] and [get or set] keys, an exception is thrown.

const foo = {};

Object.defineProperties(foo, {
    a:{ // writable預設為false
        value:33
    },
    b:{ // data descriptors,所以不能有getter和setter
        value:32,
        writable:true        
    },

})

console.log(foo.a, foo.b); // 33, 32

foo.a = 102;
foo.b = 101;

console.log(foo.a, foo.b); // 33, 101


響應式系統

響應式系統是透過Object.defineProperty()實現的,之所以它是因為要支援一些老瀏覽器(ex:IE8)

export default Vue.extend({
    name: 'Test',
    data(){
      return{
        count:33,
      }
    },
    beforeCreate(){
        console.log(this.count); // undefined
    },
    created(){
        console.log(this.count); // 33
    }
})

Vue - Lifecycle Hooks

beforeCreate階段,Vue實例(instance)已被初始化,但options還不能使用

Called immediately when the instance is initialized, after props resolution, before processing other options such as data() or computed

created階段options會被設置好,所以可以透過this讀取count的值了。但$el還沒未被設為#app或組件的template

在beforeCreate和created間會初始化響應系統(init reactivity),這時會做類似以下的動作

class Foo{
    constructor(options){
        this.initReactivity(options.data());
    }

    // 初始化響應系統
    initReactivity(data){
        const keys = Object.getOwnPropertyNames(data);    
        keys.forEach(key => {
            Object.defineProperty(this, key, {
                configurable:true,
                get(){
                    console.log('get');
                    return data[key];
                },
                set(val){
                    console.log('set');

                    if(val === data[key]) return;

                    data[key] = val;
                }
            })
        })    
    }
}

// 實例已被建立
const foo = new Foo({
  data(){
    return {
      count:33
    }
  }
});

console.log(foo.count); // 33

foo.count = 25; // 先get,後set
console.log(foo.count); // 25

無法監聽陣列元素、物件屬性的增刪、新增的屬性的變化。Map、Set、Class等,也不行

const foo = new Foo({
  data(){
    return {
      person:{
        name:'Emma',
      },
      arr:[1]
    }
  }
});

foo.arr[0] = 222; // get

foo.person.age = 12; // get

foo.num = 50; // 都沒有觸發

新手常見的畫面沒有隨資料改變重新渲染也是這個緣故


Vue3

Proxy

透過Proxy可以建立一個物件來替代原本的物件,Proxy常用於屬性的存取、驗證

接收2個參數分別為target、handler(又稱作trap),前者為原本的物件,後者為一個用於定義何時攔截、攔截時會做甚麼的物件

const person = {
  name:'alex',
}

const px = new Proxy(person, {
  get(obj, prop, val){
    console.log(`get ${prop}`);
    return obj[prop];
  },
  set(obj, prop, val){ // obj為原物件, prop為屬性名
    console.log(`set ${prop}`);
    obj[prop] = val;
  }
});

console.log(px.name); // get name

px.name = 'emma'; // set name
px.age = 25; // set name


Reflect

Reflect與Proxy用途相當類似,但它能接收第三個參數(receiver)

recevier可以視為是指定函式其中的this的指向

const obj = {
  get foo(){
    return this.foo;
  }
}

// 將this的指向改為第三個參數
console.log(Reflect.get(obj, 'foo', {foo:33})); // 33

不過Reflect並不能改變原本就有指向的this的指向

const obj = {
    foo:1,
    get bar(){
        return this.foo;
    }
}

console.log(Reflect.get(obj, 'foo', {foo:33})); // 1

Proxy加上Reflect可以完成一個簡單的響應式系統

再詳細我也不清楚了QQ


參考資料

MDN Object.defineProperty()
MDN - Proxy
一文帶你深入剖析vue3的響應式
聽說你很瞭解 Vue3 響應式?


#Vue #proxy #Object.defineProperty







Related Posts

有空的話來學一下 Tailwind CSS - Week 2

有空的話來學一下 Tailwind CSS - Week 2

#2 數位電路設計

#2 數位電路設計

網路操作指令與工具:curl, ping, telnet, nslookup

網路操作指令與工具:curl, ping, telnet, nslookup


Comments